Reduced-form VAR Modeling ========================== Description ------------ .. math:: y_{t} = C(r_{t}) x_{t} + B_{1}(r_{t}) y_{t-1} + \cdots + B_{p}(r_{t}) y_{t-p} + u_{t} with :math:`r_{t} = 1, 2, \dots, h` and transition probabilities :math:`p_{r_{t}, r_{t+1}}(I_{t})`. The modern toolbox builds the reduced-form VAR via the ``rfvar_model`` factory; the option surface uses named arguments matching the modern ``arguments`` blocks. .. contents:: :local: :depth: 2 Quick-start example -------------------- A constant-parameter five-variable VAR on Norwegian quarterly data. Collecting and transforming data ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :: % CLVMNACSCAB1GQNO : GDP Norway % IR3TIB01NOQ156N : 3-month interbank rate % NORCPGRLE01IXOBQ : CPI excluding food and energy % CCUSSP01NOQ650N : NOK / USD spot % POILWTIUSDQ : Global price of WTI crude xrange = '1990Q1:2022Q3'; rawdb = fetch_fred({'NORCPGRLE01IXOBQ','IR3TIB01NOQ156N','CLVMNACSCAB1GQNO', ... 'CCUSSP01NOQ650N','POILWTIUSDQ'}); raw.P = rawdb(1).series(xrange); raw.INTRATE = rawdb(2).series(xrange); raw.Y = rawdb(3).series(xrange); raw.EXRATE = rawdb(4).series(xrange); raw.POIL = rawdb(5).series(xrange); db = struct(); db.PAI = raw.P / lag(raw.P, 1); db.R = 1 + raw.INTRATE/100; db.GROWTH = raw.Y / lag(raw.Y, 1); db.EXRATE = 1 / raw.EXRATE; % rising => NOK depreciation db.PAIOIL = raw.POIL / lag(raw.POIL, 1); Setting up the reduced-form VAR ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :: endog = {'PAIOIL','GROWTH','PAI','R','EXRATE'}; exog = {}; nlags = 4; const = true; mdl = rfvar_model(endog, ... lag_length = nlags, ... constant_term = const, ... deterministic_vars = exog); (The legacy constructor was ``rfvar(endog, exog, nlags, const)`` positionally; the modern factory uses named arguments and a slightly different name order.) Estimating the VAR (classical / OLS) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :: mdlest = estimate(mdl, ... data = db, ... estim_start_date = date2serial(db.GROWTH.start), ... estim_end_date = date2serial(db.GROWTH.finish)); Estimation dates are passed through ``date2serial`` -- the modern date validator does not accept char strings. Restrictions on the VAR ~~~~~~~~~~~~~~~~~~~~~~~~ "Domestic variables do not affect oil prices" via linear restrictions on the VAR coefficients:: linres = { 'b1(PAIOIL,PAI)=0' 'b1(PAIOIL,GROWTH)=0' 'b1(PAIOIL,R)=0' 'b1(PAIOIL,EXRATE)=0' 'b2(PAIOIL,PAI)=0' 'b2(PAIOIL,GROWTH)=0' 'b2(PAIOIL,R)=0' 'b2(PAIOIL,EXRATE)=0' }; Or programmatically:: linres = cell(0, 1); for ilag = 1:nlags for iv = 2:numel(endog) y = endog{iv}; linres{end+1, 1} = sprintf('b%0.0f(PAIOIL,%s)=0', ilag, y); end end Estimate the restricted VAR:: mdlest_restr = estimate(mdl, ... data = db, ... estim_start_date = date2serial(db.GROWTH.start), ... estim_end_date = date2serial(db.GROWTH.finish), ... estim_linear_restrictions = linres); Identification --------------- A mixture of sign restrictions, contemporaneous zeros, and arbitrary restrictions on the impact matrix:: shock_names = {'oilp','demand','costpush','mp','forex'}; ident_restr1 = { % normalization with sign restrictions 'PAIOIL{0}@oilp','+' 'GROWTH{0}@demand','+' 'PAI{0}@costpush','+' 'R{0}@mp','+' 'EXRATE{0}@forex','+' % block: oil-price domestic neutrality 'PAIOIL{0}@demand',0 'PAIOIL{0}@costpush',0 'PAIOIL{0}@mp',0 'PAIOIL{0}@forex',0 % block: domestic-domestic 'GROWTH{0}@costpush',0 'GROWTH{0}@mp',0 'GROWTH{0}@forex',0 'PAI{0}@mp',0 'PAI{0}@forex',0 'R{0}@forex',0 }; agnostic = true; max_trials = 6000; Rfunc = struct(); ident = struct(); [Rfunc.unrestr, ident.unrestr] = ... identification(mdlest, ident_restr1, shock_names, agnostic, max_trials); [Rfunc.restr, ident.restr] = ... identification(mdlest_restr, ident_restr1, shock_names, agnostic, max_trials); Structural shocks, IRFs and decompositions ------------------------------------------- Structural shocks:: params = []; sshocks = struct(); sshocks.unrestr = structural_shocks(mdlest, params, Rfunc.unrestr, shock_names); sshocks.restr = structural_shocks(mdlest_restr, params, Rfunc.restr, shock_names); Cholesky IRFs (no identification scheme):: cholShocks = []; myirfs = irf([mdlest, mdlest_restr], cholShocks, 40); IRFs under the identification scheme:: params = []; myirfs = irf([mdlest, mdlest_restr], shock_names, 40, params, Rfunc.unrestr); Variance decomposition:: vd = variance_decomposition(mdlest_restr, params, Rfunc.restr); Historical decomposition:: hd = historical_decomposition(mdlest_restr, params, Rfunc.restr); Bootstrap and distributions ---------------------------- :: n = 1000; params = bootstrap(mdlest_restr, n); Variance-decomposition distribution:: ci = [30, 50, 68, 90]; vd = variance_decomposition(mdlest_restr, params, Rfunc.restr); % fanchart over the draws via fanchart() + plot_fanchart() Historical-decomposition distribution:: hd = historical_decomposition(mdlest_restr, params, Rfunc.restr); IRF distribution:: myirfs = irf(mdlest_restr, shock_names, 40, params, Rfunc.restr); In each case the output is multi-page (one page per bootstrap draw); pass it to ``fanchart`` for central-tendency-plus-bands plots (see :doc:`../WorkingWithAModel/Forecasting and simulation`). Bayesian estimation -------------------- VAR-coefficient priors are built with the ``var_priors`` factories. The canonical informative prior is the Minnesota family:: nlags = 4; const = true; exog = {}; var_prior = var_priors.minnesota(endog, const, exog, nlags, ... tightness = 0.1, ... lag_decay = 1.0, ... ar_first_lag = 0.9); The VAR prior is passed through ``estim_var_priors``; structural / non-VAR priors (if any) are passed through ``estim_priors``. The two options are independent and flat. Then the unrestricted Bayesian estimate:: ve = estimate(mdl, ... data = db, ... estim_start_date = date2serial(db.GROWTH.start), ... estim_end_date = date2serial(db.GROWTH.finish), ... estim_var_priors = var_prior); and the restricted Bayesian estimate:: ve_lr = estimate(mdl, ... data = db, ... estim_start_date = date2serial(db.GROWTH.start), ... estim_end_date = date2serial(db.GROWTH.finish), ... estim_var_priors = var_prior, ... estim_linear_restrictions = linres); Posterior sampling ------------------- :: params = struct(); params.ve = ve.estim_.sampler(1000); params.ve_lr = ve_lr.estim_.sampler(1000); Bayesian forecasting --------------------- :: myfkst = struct(); date_start = date2serial('2003Q1'); myfkst.ve = forecast(ve, db, date_start, params.ve); myfkst.ve_lr = forecast(ve_lr, db, date_start, params.ve_lr); The outputs are multi-page ``ts`` -- one page per posterior draw. ``fanchart`` + ``plot_fanchart`` give the central tendency plus probability bands at ``ci = [30 50 68 90]`` percent. Conditional forecasting ------------------------ Condition on a path for the policy rate ``R``:: date_start = date2serial('2003Q1'); nsteps = 12; shock_uncertainty = false; Rfunc = []; % no need for an identification scheme conditions = struct(); conditions.R = {'2003Q1','2004Q4'}; myfkst.ve = forecast(ve, db, date_start, params.ve, ... nsteps, shock_uncertainty, Rfunc, conditions); ``conditions`` carries the conditioning ranges. The full surface of conditional-forecasting options is in :doc:`../WorkingWithAModel/Forecasting and simulation`.